<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>5.4.1-合理地触发响应</title>
</head>

<body>
    <div id="container"></div>
</body>
<script>
    /**
     * 合理地触发响应，还有许多工作要做
     * 
     * 1. 当值没有发生变化时，应该不需要触发响应
     *    - 在 set 拦截函数内首先获取旧值 oldVal，
     *      接着比较新值与旧值，只有当它们不全等的时候才触发响应
     * 2. NaN 与 NaN 进行全等比较总会得到 false
     *    - 需要再加一个条件，即在新值和旧值不全等的情况下，要保证它们都不是 NaN
     * 
     * */
    const bucket = new WeakMap();
    const ITERATE_KEY = Symbol();

    // 在 get 拦截函数内调用 track 函数追踪变化
    function track(target, key) {
        // 没有 activeEffect，直接 return
        if (!activeEffect) return
        let depsMap = bucket.get(target)
        if (!depsMap) {
            bucket.set(target, (depsMap = new Map()))
        }
        let deps = depsMap.get(key)
        if (!deps) {
            depsMap.set(key, (deps = new Set()))
        }
        // 把当前激活的副作用函数添加到依赖集合 deps 中
        deps.add(activeEffect)
        // deps 就是一个与当前副作用函数存在联系的依赖集合
        // 将其添加到 activeEffect.deps 数组中
        activeEffect.deps.push(deps) 
    }
    // 在 set 拦截函数内调用 trigger 函数触发变化
    function trigger(target, key, type) {
        const depsMap = bucket.get(target)
        if (!depsMap) return
        // 取得与 key 相关联的副作用函数
        const effects = depsMap.get(key)

        const effectsToRun = new Set()
        // 将与 key 相关联的副作用函数添加到 effectsToRun
        effects && effects.forEach(effectFn => {
            // 如果 trigger 触发执行的副作用函数与当前正在执行的副作用函数相同，则不触发执行
            if (effectFn !== activeEffect) {  
                effectsToRun.add(effectFn)
            }
        })

        // 只有当操作类型为 'ADD' 时或 DELETE 时，才触发与 ITERATE_KEY 相关联的副作用函数重新执行
        if (type === 'ADD' || type === 'DELETE') {
            // 取得与 ITERATE_KEY 相关联的副作用函数
            const iterateEffects = depsMap.get(ITERATE_KEY)
            // 将与 ITERATE_KEY 相关联的副作用函数也添加到 effectsToRun
            iterateEffects && iterateEffects.forEach(effectFn => { 
                if (effectFn !== activeEffect) {  
                    effectsToRun.add(effectFn)
                }
            })
        }

        effectsToRun.forEach(effectFn => {
            // 如果一个副作用函数存在调度器，则调用该调度器，并将副作用函数作为参数传递
            if (effectFn.options.scheduler) {  
                effectFn.options.scheduler(effectFn)  
            } else {
                // 否则直接执行副作用函数（之前的默认行为）
                effectFn() 
            }
        })
    }

    // 用一个全局变量存储被注册的副作用函数
    let activeEffect
    // effect 栈
    // 个人理解，始终都会留着一个是最先执行的
    const effectStack = []  
    function effect(fn, options = {}) {
        const effectFn = () => {
            cleanup(effectFn)
            // 当调用 effect 注册副作用函数时，将副作用函数赋值给 activeEffect
            activeEffect = effectFn
            // 在调用副作用函数之前将当前副作用函数压栈
            effectStack.push(effectFn)
            // 将 fn 的执行结果存储到 res 中
            const res = fn()  
            // 在当前副作用函数执行完毕后，将当前副作用函数弹出栈，并把 activeEffect 还原为之前的值
            effectStack.pop()
            activeEffect = effectStack[effectStack.length - 1]
            return res  
        }
        // 将 options 挂载到 effectFn 上
        effectFn.options = options 
        // activeEffect.deps 用来存储所有与该副作用函数相关的依赖集合
        effectFn.deps = []
        // 只有非 lazy 的时候，才执行
        if (!options.lazy) {  
            // 执行副作用函数
            effectFn()
        }
        // 将副作用函数作为返回值返回
        return effectFn 
    }

    function cleanup(effectFn) {
        // 遍历 effectFn.deps 数组
        for (let i = 0; i < effectFn.deps.length; i++) {
            // deps 是依赖集合
            const deps = effectFn.deps[i]
            // 将 effectFn 从依赖集合中移除
            deps.delete(effectFn)
        }
        // 最后需要重置 effectFn.deps 数组
        effectFn.deps.length = 0
    }

    const obj = { foo: 1, bar: 2}

    const p = new Proxy(obj, {
        get(target, key, receiver) {
            // 建立联系
            track(target, key)
            // 返回属性值
            return Reflect.get(target, key, receiver)
        },
        // 拦截设置操作
        set(target, key, newVal, receiver) {
            // 先获取旧值
            const oldVal = target[key]  // 新增

            // 如果属性不存在，则说明是在添加新属性，否则是设置已有属性
            const type = Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD'

            // 设置属性值
            const res = Reflect.set(target, key, newVal, receiver)
            // 比较新值与旧值，只有当它们不全等，并且不都是 NaN 的时候才触发响应
            if (oldVal !== newVal  && (oldVal === oldVal || newVal === newVal)) {  // 新增
                // 把副作用函数从桶里取出并执行
                trigger(target, key, type)
            }
            return res;
        },
        // 拦截 in 操作符
        has(target, key) {
            // 建立联系
            track(target, key)
            // 返回属性值
            return Reflect.has(target, key)
        },
        // 拦截for...in循环
        ownKeys(target) {
            // 将副作用函数与 ITERATE_KEY 关联
            track(target, ITERATE_KEY)
            return Reflect.ownKeys(target)
        },
        deleteProperty(target, key) {
            // 检查被操作的属性是否是对象自己的属性
            const hadKey = Object.prototype.hasOwnProperty.call(target, key)
            // 使用 Reflect.deleteProperty 完成属性的删除
            const res = Reflect.deleteProperty(target, key)
            if (res && hadKey) {
                // 只有当被删除的属性是对象自己的属性并且成功删除时，才触发更新
                trigger(target, key, 'DELETE')
            }
            return res
        },
    })

    effect(() => {
        // for...in 循环
        console.log(p.foo)
    })
    setTimeout(() => {
        p.foo = 1
    }, 1000)

</script>

</html>